﻿using Dapper;
using Dapper.Contrib.Extensions;
using Newtonsoft.Json;
using SimulationCenter.Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SimulationCenter.Tasks.Svrs
{
    public class TaskSvr : ITaskSvr
    {
        /// <summary>
        /// 初始任务
        /// </summary>
        public const string START_UP_TASK = "StartUpTask";

        /// <summary>
        /// 周期任务
        /// </summary>
        public const string Daily_Task = "DailyTask";

        private readonly SqlProvider _sqlProvider;
        private readonly ISubscriptionSvr _subscriptionSvr;

        public TaskSvr(SqlProvider sqlProvider, ISubscriptionSvr subscriptionSvr)
        {
            _sqlProvider = sqlProvider;
            _subscriptionSvr = subscriptionSvr;
        }

        /// <summary>
        /// 获取所有需要恢复的任务
        /// </summary>
        /// <returns></returns>
        public async Task<List<ScTask>> GetRecoveryTasks()
        {
            var sql = "SELECT * FROM ScTask WHERE STATUS = @Running";
            var param = new { ScTaskStatus.Running };
            IEnumerable<ScTask> scTasks = null;

            _sqlProvider.Executing(conn =>
            {
                scTasks = conn.Query<ScTask>(sql, param);
            });

            await Task.CompletedTask;

            return scTasks.ToList();
        }

        /// <summary>
        /// 根据编号获取任务
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<ScTask> GetTask(int id)
        {
            var sql = "SELECT * FROM ScTask WHERE ID = @id";
            var param = new { id };

            ScTask scTask = null;

            _sqlProvider.Executing(conn =>
           {
               scTask = conn.QueryFirst<ScTask>(sql, param);
           });

            await Task.CompletedTask;
            return scTask;
        }

        /// <summary>
        /// 尝试获取下一个设备子任务
        /// </summary>
        /// <param name="scTaskId"></param>
        /// <param name="device"></param>
        /// <returns></returns>
        public async Task<ScDeviceTask> TryGetNextDeviceTask(int scTaskId, string device)
        {
            var sql = "SELECT * FROM ScDeviceTask WHERE ScTaskId = @scTaskId AND SERIAL = @device AND STATUS = @Ready LIMIT 1;";
            var param = new { scTaskId, device, ScTaskStatus.Ready };

            ScDeviceTask deviceTask = null;
            _sqlProvider.Executing(conn => deviceTask = conn.QueryFirstOrDefault<ScDeviceTask>(sql, param));
            await Task.CompletedTask;
            return deviceTask;
        }

        /// <summary>
        /// 创建子任务
        /// </summary>
        /// <param name="scTaskId"></param>
        /// <param name="device"></param>
        /// <param name="subscription"></param>
        /// <returns></returns>
        public async Task BindDeviceTask(int scTaskId, string device, string subscription)
        {
            var sql = "SELECT * FROM ScDeviceTask WHERE ScTaskId = @scTaskId AND SERIAL = @device AND SUBSCRIPTION = @subscription LIMIT 1";
            var param = new { scTaskId, device, subscription };

            ScDeviceTask deviceTask = null;
            _sqlProvider.Executing(conn => deviceTask = conn.QueryFirstOrDefault<ScDeviceTask>(sql, param));

            if (deviceTask == null)
            {
                _sqlProvider.Executing(conn => conn.Insert(new ScDeviceTask
                {
                    ScTaskId = scTaskId,
                    Serial = device,
                    Subscription = subscription,
                    Status = ScTaskStatus.Ready
                }));
            }
            else
            {
                deviceTask.Status = ScTaskStatus.Ready;
                deviceTask.StartAt = null;
                deviceTask.EndAt = null;

                _sqlProvider.Executing(conn => conn.Update(deviceTask));
            }

            await Task.CompletedTask;
        }

        public async Task UpdateTask(ScTask scTask)
        {
            _sqlProvider.Executing(conn => conn.UpdateAsync(scTask));
            await Task.CompletedTask;
        }

        public async Task UpdateDeviceTask(ScDeviceTask scDeviceTask)
        {
            _sqlProvider.Executing(conn => conn.Update(scDeviceTask));
            await Task.CompletedTask;
        }

        public List<ScDeviceTask> GetDeviceTasksBySubscription(string subscription)
        {
            var scSubscription = _subscriptionSvr.FindSubscription(subscription);

            var sql = "SELECT * FROM ScDeviceTask WHERE SUBSCRIPTION = @subscription AND (`STATUS` = 3  OR `STATUS` = 1) " +
                      "AND ScTaskId IN (SELECT Id FROM ScTask WHERE STATUS = @Running)";
            var param = new
            {
                subscription = scSubscription != null ? scSubscription.Subscription : subscription,
                ScTaskStatus.Running
            };

            IEnumerable<ScDeviceTask> deviceTasks = null;
            _sqlProvider.Executing(conn => deviceTasks = conn.Query<ScDeviceTask>(sql, param));
            return deviceTasks.ToList();
        }

        /// <summary>
        /// 创建主任务
        /// </summary>
        /// <param name="name"></param>
        /// <param name="description"></param>
        /// <param name="subscriptionRange"></param>
        /// <param name="targetSubscriptions"></param>
        /// <param name="targetDevices"></param>
        /// <param name="status"></param>
        /// <param name="msgRange"></param>
        /// <param name="lastDays"></param>
        /// <param name="loopMode"></param>
        /// <param name="cron"></param>
        /// <returns></returns>
        public async Task<ScTask> CreateTask(string name, string description, ScSubscriptionRange subscriptionRange, string targetDevices, ScTaskStatus status = ScTaskStatus.Ready, string targetSubscriptions = null,
            ScMsgRange msgRange = ScMsgRange.All, int? lastDays = null, ScLoopMode loopMode = ScLoopMode.Once, string cron = null)
        {
            var scTask = new ScTask
            {
                Name = name,
                Description = description,
                SubscriptionRange = subscriptionRange,
                TargetSubscriptions = targetSubscriptions,
                TargetDevices = targetDevices,
                MsgRange = msgRange,
                LastDays = lastDays,
                LoopMode = loopMode,
                Cron = cron,
                Status = status
            };

            long scTaskId = 0;
            _sqlProvider.Executing(conn => scTaskId = conn.Insert(scTask));

            // 指定公众号情况下，绑定任务，设备，公众号三者之间的关联
            if (subscriptionRange == ScSubscriptionRange.Special && targetSubscriptions != null)
            {
                var devices = targetDevices.Split(",");
                var subscriptions = targetSubscriptions.Split(",").Distinct().ToArray();

                // 未分配的公众号（指定设备中不存在与此公众号有关联的记录）
                var unallocated = new Queue<string>();

                foreach (var subscription in subscriptions)
                {
                    // 先查找指定设备中已经关注此公众号的设备
                    var sql = "SELECT * FROM ScDeviceSubscription WHERE Subscription = @subscription";
                    var param = new { subscription };

                    IEnumerable<ScDeviceSubscription> deviceSubscriptions = null;
                    _sqlProvider.Executing(conn => deviceSubscriptions = conn.Query<ScDeviceSubscription>(sql, param));

                    // 筛选与 target devices 匹配的记录
                    deviceSubscriptions = deviceSubscriptions.Where(s => devices.Contains(s.Serial));
                    var scDeviceSubscriptions = deviceSubscriptions as ScDeviceSubscription[] ?? deviceSubscriptions.ToArray();

                    if (scDeviceSubscriptions.Any())
                    {
                        var deviceSubscription = scDeviceSubscriptions.OrderByDescending(s => s.UpdateAt).First();

                        await _subscriptionSvr.BindDeviceSubscription(deviceSubscription.Serial, subscription);
                        await BindDeviceTask((int)scTaskId, deviceSubscription.Serial, subscription);
                    }
                    else
                    {
                        unallocated.Enqueue(subscription);
                    }
                }

                // 处理未分配的公众号
                {
                    var average = subscriptions.Length % devices.Length == 0 ?
                        subscriptions.Length / devices.Length :
                        subscriptions.Length / devices.Length + 1;

                    for (var i = 0; i < devices.Length; i++)
                    {
                        var subs = subscriptions.Skip(i * average).Take(average);

                        foreach (var sub in subs)
                        {
                            await _subscriptionSvr.BindDeviceSubscription(devices[i], sub);

                            await BindDeviceTask((int)scTaskId, devices[i], sub);
                        }
                    }
                }
            }

            return scTask;
        }

        /// <summary>
        /// 添加初始任务
        /// </summary>
        /// <returns></returns>
        public async Task AddStartUpTasks(List<string> deviceSerials)
        {
            if (deviceSerials == null || !deviceSerials.Any()) return;

            //var sql = "SELECT COUNT(1) FROM ScTask WHERE STATUS = @Running";
            //var param = new { ScTaskStatus.Running };

            //var exists = (await _sqlProvider.Connection.ExecuteScalarAsync<int>(sql, param)) > 0;
            //if (exists) return;

            // 挂起原先的任务
            var sql = "UPDATE ScTask Set STATUS = @Suspend WHERE STATUS <> @Complete";
            var param = new { ScTaskStatus.Suspend, ScTaskStatus.Complete };
            _sqlProvider.Executing(conn => conn.Execute(sql, param));

            var targetDevices = string.Join(",", deviceSerials);

            var scTasks = await GetTaskFromStartup();

            if (scTasks != null && scTasks.Any())
            {
                foreach (var scTask in scTasks)
                {
                    await CreateTask(
                        scTask.Name,
                        scTask.Description,
                        scTask.SubscriptionRange,
                        targetDevices,
                        scTask.Status,
                        scTask.TargetSubscriptions,
                        scTask.MsgRange,
                        scTask.LastDays,
                        scTask.LoopMode,
                        scTask.Cron);
                }
            }
            else
            {
                await CreateTask(START_UP_TASK, "初始任务", ScSubscriptionRange.All, targetDevices, ScTaskStatus.Running,
                    msgRange: ScMsgRange.LastDays,
                    lastDays: 180);
                await CreateTask(Daily_Task, "周期任务", ScSubscriptionRange.All, targetDevices, ScTaskStatus.Running,
                    msgRange: ScMsgRange.LastDays,
                    lastDays: 1,
                    loopMode: ScLoopMode.Cron,
                    cron: "0 0 0 1/1 * ?");
            }
        }

        /// <summary>
        /// 是否还存在其他设备任务
        /// </summary>
        /// <param name="scTaskId"></param>
        /// <param name="device"></param>
        /// <returns></returns>
        public async Task<bool> ExistsOtherDeviceTask(int scTaskId, string device)
        {
            var sql = "SELECT * FROM ScDeviceTask WHERE Serial <> @device AND ScTaskId = @scTaskId AND STATUS = @Ready";
            var param = new { device, scTaskId, ScTaskStatus.Ready };

            IEnumerable<ScDeviceTask> scDeviceTasks = null;
            _sqlProvider.Executing(conn => scDeviceTasks = conn.Query<ScDeviceTask>(sql, param));
            await Task.CompletedTask;
            return scDeviceTasks.Any();
        }

        /// <summary>
        /// 获取正在运行的初始化任务
        /// </summary>
        /// <returns></returns>
        public async Task<ScTask> GetRunningStartUpTask()
        {
            var sql = "SELECT * FROM ScTask WHERE NAME = @name AND STATUS = @Running LIMIT 1";
            var param = new { name = START_UP_TASK, ScTaskStatus.Running };

            ScTask scTask = null;
            _sqlProvider.Executing(conn => scTask = conn.QuerySingleOrDefault(sql, param));
            await Task.CompletedTask;
            return scTask;
        }

        private async Task<List<ScTask>> GetTaskFromStartup()
        {
            try
            {
                var src = "startup.json";

                if (!File.Exists(src)) return null;

                src = await File.ReadAllTextAsync(src, Encoding.UTF8);

                return JsonConvert.DeserializeObject<List<ScTask>>(src);
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}
